/** @file
Functions implementation related with DHCPv4/v6 for DNS driver.

Copyright (c) 2015, Intel Corporation. All rights reserved.<BR>
This program and the accompanying materials
are licensed and made available under the terms and conditions of the BSD License
which accompanies this distribution.  The full text of the license may be found at
http://opensource.org/licenses/bsd-license.php

THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.

**/

#include "DnsImpl.h"

/**
  The callback function for the timer event used to get map.

  @param[in] Event    The event this function is registered to.
  @param[in] Context  The context registered to the event.
**/
VOID
EFIAPI
TimeoutToGetMap (
  IN EFI_EVENT      Event,
  IN VOID           *Context
  )
{
  *((BOOLEAN *) Context) = TRUE;
  return ;
}

/**
  Create an IP child, use it to start the auto configuration, then destroy it.

  @param[in] Controller       The controller which has the service installed.
  @param[in] Image            The image handle used to open service.

  @retval EFI_SUCCESS         The configuration is done.
  @retval Others              Other errors as indicated.
**/
EFI_STATUS
EFIAPI
DnsStartIp4(
  IN  EFI_HANDLE            Controller,
  IN  EFI_HANDLE            Image
  )
{
  EFI_IP4_PROTOCOL              *Ip4;
  EFI_HANDLE                    Ip4Handle;
  EFI_EVENT                     TimerToGetMap;
  EFI_IP4_CONFIG_DATA           Ip4ConfigData;
  EFI_IP4_MODE_DATA             Ip4Mode;
  EFI_STATUS                    Status;

  BOOLEAN                       Timeout;

  //
  // Get the Ip4ServiceBinding Protocol
  //
  Ip4Handle     = NULL;
  Ip4           = NULL;
  TimerToGetMap = NULL;
  
  Timeout      = FALSE;

  Status = NetLibCreateServiceChild (
             Controller,
             Image,
             &gEfiIp4ServiceBindingProtocolGuid,
             &Ip4Handle
             );

  if (EFI_ERROR (Status)) {
    return Status;
  }

  Status = gBS->OpenProtocol (
                 Ip4Handle,
                 &gEfiIp4ProtocolGuid,
                 (VOID **) &Ip4,
                 Controller,
                 Image,
                 EFI_OPEN_PROTOCOL_GET_PROTOCOL
                 );

  if (EFI_ERROR (Status)) {
    goto ON_EXIT;
  }

  Ip4ConfigData.DefaultProtocol          = EFI_IP_PROTO_ICMP;
  Ip4ConfigData.AcceptAnyProtocol        = FALSE;
  Ip4ConfigData.AcceptIcmpErrors         = FALSE;
  Ip4ConfigData.AcceptBroadcast          = FALSE;
  Ip4ConfigData.AcceptPromiscuous        = FALSE;
  Ip4ConfigData.UseDefaultAddress        = TRUE;
  ZeroMem (&Ip4ConfigData.StationAddress, sizeof (EFI_IPv4_ADDRESS));
  ZeroMem (&Ip4ConfigData.SubnetMask, sizeof (EFI_IPv4_ADDRESS));
  Ip4ConfigData.TypeOfService            = 0;
  Ip4ConfigData.TimeToLive               = 1;
  Ip4ConfigData.DoNotFragment            = FALSE;
  Ip4ConfigData.RawData                  = FALSE;
  Ip4ConfigData.ReceiveTimeout           = 0;
  Ip4ConfigData.TransmitTimeout          = 0;

  Status = Ip4->Configure (Ip4, &Ip4ConfigData);

  if (Status == EFI_NO_MAPPING) {
    Status  = gBS->CreateEvent (
                    EVT_NOTIFY_SIGNAL | EVT_TIMER,
                    TPL_CALLBACK,
                    TimeoutToGetMap,
                    &Timeout,
                    &TimerToGetMap
                    );
    
    if (EFI_ERROR (Status)) {
      goto ON_EXIT;
    }
    
    Status = gBS->SetTimer (
                   TimerToGetMap,
                   TimerRelative,
                   MultU64x32 (10000000, 5)
                   );
    
    if (EFI_ERROR (Status)) {
      goto ON_EXIT;
    }
    
    while (!Timeout) {
      Ip4->Poll (Ip4);
  
      if (!EFI_ERROR (Ip4->GetModeData (Ip4, &Ip4Mode, NULL, NULL)) && 
          Ip4Mode.IsConfigured) {       
        break;
      }
    }

    if (Timeout) {
      Status = EFI_DEVICE_ERROR;
    }
  }
  
ON_EXIT: 

  if (TimerToGetMap != NULL) {
    gBS->SetTimer (TimerToGetMap, TimerCancel, 0);
    gBS->CloseEvent (TimerToGetMap);
  }

  NetLibDestroyServiceChild (
    Controller,
    Image,
    &gEfiIp4ServiceBindingProtocolGuid,
    Ip4Handle
    );
  
  return Status;
}

/**
  This function initialize the DHCP4 message instance.

  This function will pad each item of dhcp4 message packet.

  @param  Seed             Pointer to the message instance of the DHCP4 packet.
  @param  InterfaceInfo    Pointer to the EFI_IP4_CONFIG2_INTERFACE_INFO instance.

**/
VOID
DnsInitSeedPacket (
  OUT EFI_DHCP4_PACKET               *Seed,
  IN  EFI_IP4_CONFIG2_INTERFACE_INFO *InterfaceInfo
  )
{
  EFI_DHCP4_HEADER           *Header;

  //
  // Get IfType and HwAddressSize from SNP mode data.
  //
  Seed->Size            = sizeof (EFI_DHCP4_PACKET);
  Seed->Length          = sizeof (Seed->Dhcp4);
  Header                = &Seed->Dhcp4.Header;
  ZeroMem (Header, sizeof (EFI_DHCP4_HEADER));
  Header->OpCode        = DHCP4_OPCODE_REQUEST;
  Header->HwType        = InterfaceInfo->IfType;
  Header->HwAddrLen     = (UINT8) InterfaceInfo->HwAddressSize;
  CopyMem (Header->ClientHwAddr, &(InterfaceInfo->HwAddress), Header->HwAddrLen);

  Seed->Dhcp4.Magik     = DHCP4_MAGIC;
  Seed->Dhcp4.Option[0] = DHCP4_TAG_EOP;
}

/**
  The common notify function. 

  @param[in]  Event   The event signaled.
  @param[in]  Context The context.

**/
VOID
EFIAPI
DhcpCommonNotify (
  IN EFI_EVENT  Event,
  IN VOID       *Context
  )
{
  if ((Event == NULL) || (Context == NULL)) {
    return ;
  }

  *((BOOLEAN *) Context) = TRUE;
}

/**
  Parse the ACK to get required information

  @param  Dhcp4            The DHCP4 protocol.
  @param  Packet           Packet waiting for parse.
  @param  DnsServerInfor   The required Dns4 server information.

  @retval EFI_SUCCESS           The DNS information is got from the DHCP ACK.
  @retval EFI_NO_MAPPING        DHCP failed to acquire address and other information.
  @retval EFI_DEVICE_ERROR      Other errors as indicated.
  @retval EFI_OUT_OF_RESOURCES  Failed to allocate memory.

**/
EFI_STATUS
ParseDhcp4Ack (
  IN EFI_DHCP4_PROTOCOL         *Dhcp4,
  IN EFI_DHCP4_PACKET           *Packet,
  IN DNS4_SERVER_INFOR          *DnsServerInfor
  )
{
  EFI_STATUS              Status;
  UINT32                  OptionCount;
  EFI_DHCP4_PACKET_OPTION **OptionList;
  UINT32                  ServerCount;
  EFI_IPv4_ADDRESS        *ServerList;
  UINT32                  Index;
  UINT32                  Count;

  ServerCount = 0;
  ServerList = NULL;

  OptionCount = 0;
  OptionList  = NULL;

  Status      = Dhcp4->Parse (Dhcp4, Packet, &OptionCount, OptionList);
  if (Status != EFI_BUFFER_TOO_SMALL) {
    return EFI_DEVICE_ERROR;
  }

  OptionList = AllocatePool (OptionCount * sizeof (EFI_DHCP4_PACKET_OPTION *));
  if (OptionList == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  Status = Dhcp4->Parse (Dhcp4, Packet, &OptionCount, OptionList);
  if (EFI_ERROR (Status)) {
    gBS->FreePool (OptionList);
    return EFI_DEVICE_ERROR;
  }

  Status = EFI_NOT_FOUND;

  for (Index = 0; Index < OptionCount; Index++) {
    //
    // Get DNS server addresses
    //
    if (OptionList[Index]->OpCode == DHCP4_TAG_DNS_SERVER) {

      if (((OptionList[Index]->Length & 0x3) != 0) || (OptionList[Index]->Length == 0)) {
        Status = EFI_DEVICE_ERROR;
        break;
      }

      ServerCount = OptionList[Index]->Length/4;
      ServerList = AllocatePool (ServerCount * sizeof (EFI_IPv4_ADDRESS));
      if (ServerList == NULL) {
        return EFI_OUT_OF_RESOURCES;
      }

      for(Count=0; Count < ServerCount; Count++){
        CopyMem (ServerList + Count, &OptionList[Index]->Data[4 * Count], sizeof (EFI_IPv4_ADDRESS));
      }

      *(DnsServerInfor->ServerCount) = ServerCount;
      DnsServerInfor->ServerList     = ServerList;

      Status = EFI_SUCCESS;
    }
  }

  gBS->FreePool (OptionList);
  
  return Status;
}

/**
  EFI_DHCP6_INFO_CALLBACK is provided by the consumer of the EFI DHCPv6 Protocol 
  instance to intercept events that occurs in the DHCPv6 Information Request
  exchange process.

  @param  This                  Pointer to the EFI_DHCP6_PROTOCOL instance that 
                                is used to configure this  callback function.
  @param  Context               Pointer to the context that is initialized in
                                the EFI_DHCP6_PROTOCOL.InfoRequest().
  @param  Packet                Pointer to Reply packet that has been received.
                                The EFI DHCPv6 Protocol instance is responsible
                                for freeing the buffer.

  @retval EFI_SUCCESS           The DNS information is got from the DHCP ACK.
  @retval EFI_DEVICE_ERROR      Other errors as indicated.
  @retval EFI_OUT_OF_RESOURCES  Failed to allocate memory.
**/
EFI_STATUS
EFIAPI
ParseDhcp6Ack (
  IN EFI_DHCP6_PROTOCOL          *This,
  IN VOID                        *Context,
  IN EFI_DHCP6_PACKET            *Packet
  )
{
  EFI_STATUS                  Status;
  UINT32                      OptionCount;
  EFI_DHCP6_PACKET_OPTION     **OptionList;
  DNS6_SERVER_INFOR           *DnsServerInfor;
  UINT32                      ServerCount;
  EFI_IPv6_ADDRESS            *ServerList;
  UINT32                      Index;
  UINT32                      Count;
 
  OptionCount = 0;
  ServerCount = 0;
  ServerList  = NULL;
  
  Status      = This->Parse (This, Packet, &OptionCount, NULL);
  if (Status != EFI_BUFFER_TOO_SMALL) {
    return EFI_DEVICE_ERROR;
  }

  OptionList = AllocateZeroPool (OptionCount * sizeof (EFI_DHCP6_PACKET_OPTION *));
  if (OptionList == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  Status = This->Parse (This, Packet, &OptionCount, OptionList);
  if (EFI_ERROR (Status)) {
    gBS->FreePool (OptionList);
    return EFI_DEVICE_ERROR;
  }
  
  DnsServerInfor = (DNS6_SERVER_INFOR *) Context;

  for (Index = 0; Index < OptionCount; Index++) {
    OptionList[Index]->OpCode = NTOHS (OptionList[Index]->OpCode);
    OptionList[Index]->OpLen  = NTOHS (OptionList[Index]->OpLen);

    //
    // Get DNS server addresses from this reply packet.
    //
    if (OptionList[Index]->OpCode == DHCP6_TAG_DNS_SERVER) {

      if (((OptionList[Index]->OpLen & 0xf) != 0) || (OptionList[Index]->OpLen == 0)) {
        Status = EFI_DEVICE_ERROR;
        gBS->FreePool (OptionList);
        return Status;
      }
      
      ServerCount = OptionList[Index]->OpLen/16;
      ServerList = AllocatePool (ServerCount * sizeof (EFI_IPv6_ADDRESS));
      if (ServerList == NULL) {
        gBS->FreePool (OptionList);
        return EFI_OUT_OF_RESOURCES;
      }

      for(Count=0; Count < ServerCount; Count++){
        CopyMem (ServerList + Count, &OptionList[Index]->Data[16 * Count], sizeof (EFI_IPv6_ADDRESS));
      }

      *(DnsServerInfor->ServerCount) = ServerCount;
      DnsServerInfor->ServerList     = ServerList;
    }
  }

  gBS->FreePool (OptionList);
  
  return Status;

}

/**
  Parse the DHCP ACK to get Dns4 server information.

  @param  Instance         The DNS instance.
  @param  DnsServerCount   Retrieved Dns4 server Ip count.
  @param  DnsServerList    Retrieved Dns4 server Ip list.

  @retval EFI_SUCCESS           The Dns4 information is got from the DHCP ACK.
  @retval EFI_OUT_OF_RESOURCES  Failed to allocate memory.
  @retval EFI_NO_MEDIA          There was a media error.
  @retval Others                Other errors as indicated.

**/
EFI_STATUS
GetDns4ServerFromDhcp4 (
  IN  DNS_INSTANCE               *Instance,
  OUT UINT32                     *DnsServerCount,
  OUT EFI_IPv4_ADDRESS           **DnsServerList
  )
{
  EFI_STATUS                          Status;
  EFI_HANDLE                          Image;
  EFI_HANDLE                          Controller;
  BOOLEAN                             MediaPresent;
  EFI_HANDLE                          MnpChildHandle;  
  EFI_MANAGED_NETWORK_PROTOCOL        *Mnp;
  EFI_MANAGED_NETWORK_CONFIG_DATA     MnpConfigData;
  EFI_HANDLE                          Dhcp4Handle;  
  EFI_DHCP4_PROTOCOL                  *Dhcp4;
  EFI_IP4_CONFIG2_PROTOCOL            *Ip4Config2;
  UINTN                               DataSize;
  VOID                                *Data;
  EFI_IP4_CONFIG2_INTERFACE_INFO      *InterfaceInfo;
  EFI_DHCP4_PACKET                    SeedPacket;
  EFI_DHCP4_PACKET_OPTION             *ParaList[2];
  DNS4_SERVER_INFOR                   DnsServerInfor;

  EFI_DHCP4_TRANSMIT_RECEIVE_TOKEN    Token;
  BOOLEAN                             IsDone;
  UINTN                               Index;
  
  Image                      = Instance->Service->ImageHandle;
  Controller                 = Instance->Service->ControllerHandle;

  MnpChildHandle             = NULL;
  Mnp                        = NULL;
  
  Dhcp4Handle                = NULL;
  Dhcp4                      = NULL;

  Ip4Config2                 = NULL;
  DataSize                   = 0;
  Data                       = NULL;
  InterfaceInfo              = NULL;

  ZeroMem ((UINT8 *) ParaList, sizeof (ParaList));

  ZeroMem (&MnpConfigData, sizeof (EFI_MANAGED_NETWORK_CONFIG_DATA));
  
  ZeroMem (&DnsServerInfor, sizeof (DNS4_SERVER_INFOR));
  
  ZeroMem (&Token, sizeof (EFI_DHCP4_TRANSMIT_RECEIVE_TOKEN));
  
  DnsServerInfor.ServerCount = DnsServerCount;

  IsDone = FALSE;

  //
  // Check media.
  //
  MediaPresent = TRUE;
  NetLibDetectMedia (Controller, &MediaPresent);
  if (!MediaPresent) {
    return EFI_NO_MEDIA;
  }

  //
  // Start the auto configuration if UseDefaultSetting.
  //
  if (Instance->Dns4CfgData.UseDefaultSetting) {
    Status = DnsStartIp4 (Controller, Image);
    if (EFI_ERROR(Status)) {
      return Status;
    }
  }
  
  //
  // Create a Mnp child instance, get the protocol and config for it.
  //
  Status = NetLibCreateServiceChild (
             Controller,
             Image,
             &gEfiManagedNetworkServiceBindingProtocolGuid,
             &MnpChildHandle
             );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  Status = gBS->OpenProtocol (
                  MnpChildHandle,
                  &gEfiManagedNetworkProtocolGuid,
                  (VOID **) &Mnp,
                  Image,
                  Controller,
                  EFI_OPEN_PROTOCOL_BY_DRIVER
                  );
  if (EFI_ERROR (Status)) {
    goto ON_EXIT;
  }
  
  MnpConfigData.ReceivedQueueTimeoutValue = 0;
  MnpConfigData.TransmitQueueTimeoutValue = 0;
  MnpConfigData.ProtocolTypeFilter        = IP4_ETHER_PROTO;
  MnpConfigData.EnableUnicastReceive      = TRUE;
  MnpConfigData.EnableMulticastReceive    = TRUE;
  MnpConfigData.EnableBroadcastReceive    = TRUE;
  MnpConfigData.EnablePromiscuousReceive  = FALSE;
  MnpConfigData.FlushQueuesOnReset        = TRUE;
  MnpConfigData.EnableReceiveTimestamps   = FALSE;
  MnpConfigData.DisableBackgroundPolling  = FALSE;

  Status = Mnp->Configure(Mnp, &MnpConfigData);
  if (EFI_ERROR (Status)) {
    goto ON_EXIT;
  }
  
  //
  // Create a DHCP4 child instance and get the protocol.
  //
  Status = NetLibCreateServiceChild (
             Controller,
             Image,
             &gEfiDhcp4ServiceBindingProtocolGuid,
             &Dhcp4Handle
             );
  if (EFI_ERROR (Status)) {
    goto ON_EXIT;
  }

  Status = gBS->OpenProtocol (
                  Dhcp4Handle,
                  &gEfiDhcp4ProtocolGuid,
                  (VOID **) &Dhcp4,
                  Image,
                  Controller,
                  EFI_OPEN_PROTOCOL_BY_DRIVER
                  );
  if (EFI_ERROR (Status)) {
    goto ON_EXIT;
  }

  //
  // Get Ip4Config2 instance info.
  //
  Status = gBS->HandleProtocol (Controller, &gEfiIp4Config2ProtocolGuid, (VOID **) &Ip4Config2);
  if (EFI_ERROR (Status)) {
    goto ON_EXIT;
  }
  
  Status = Ip4Config2->GetData (Ip4Config2, Ip4Config2DataTypeInterfaceInfo, &DataSize, Data);
  if (EFI_ERROR (Status) && Status != EFI_BUFFER_TOO_SMALL) {
    goto ON_EXIT;
  }

  Data = AllocateZeroPool (DataSize);
  if (Data == NULL) {
    Status = EFI_OUT_OF_RESOURCES;
    goto ON_EXIT;
  }

  Status = Ip4Config2->GetData (Ip4Config2, Ip4Config2DataTypeInterfaceInfo, &DataSize, Data);
  if (EFI_ERROR (Status)) {
    goto ON_EXIT;
  }

  InterfaceInfo = (EFI_IP4_CONFIG2_INTERFACE_INFO *)Data;
  
  //
  // Build required Token.
  //
  Status = gBS->CreateEvent (
                  EVT_NOTIFY_SIGNAL,
                  TPL_NOTIFY,
                  DhcpCommonNotify,
                  &IsDone,
                  &Token.CompletionEvent
                  );
  if (EFI_ERROR (Status)) {
    goto ON_EXIT;
  }
  
  SetMem (&Token.RemoteAddress, sizeof (EFI_IPv4_ADDRESS), 0xff);
  
  Token.RemotePort = 67;

  Token.ListenPointCount = 1;
  
  Token.ListenPoints = AllocateZeroPool (Token.ListenPointCount * sizeof (EFI_DHCP4_LISTEN_POINT));
  if (Token.ListenPoints == NULL) {
    Status = EFI_OUT_OF_RESOURCES;
    goto ON_EXIT;
  }

  if (Instance->Dns4CfgData.UseDefaultSetting) {
    CopyMem (&(Token.ListenPoints[0].ListenAddress), &(InterfaceInfo->StationAddress), sizeof (EFI_IPv4_ADDRESS));
    CopyMem (&(Token.ListenPoints[0].SubnetMask), &(InterfaceInfo->SubnetMask), sizeof (EFI_IPv4_ADDRESS));
  } else {
    CopyMem (&(Token.ListenPoints[0].ListenAddress), &(Instance->Dns4CfgData.StationIp), sizeof (EFI_IPv4_ADDRESS));
    CopyMem (&(Token.ListenPoints[0].SubnetMask), &(Instance->Dns4CfgData.SubnetMask), sizeof (EFI_IPv4_ADDRESS));
  }
  
  Token.ListenPoints[0].ListenPort = 68;
  
  Token.TimeoutValue = DNS_TIME_TO_GETMAP;

  DnsInitSeedPacket (&SeedPacket, InterfaceInfo);

  ParaList[0] = AllocateZeroPool (sizeof (EFI_DHCP4_PACKET_OPTION));
  if (ParaList[0] == NULL) {
    Status = EFI_OUT_OF_RESOURCES;
    goto ON_EXIT;
  }
  
  ParaList[0]->OpCode  = DHCP4_TAG_TYPE;
  ParaList[0]->Length  = 1;
  ParaList[0]->Data[0] = DHCP4_MSG_INFORM;
  
  ParaList[1] = AllocateZeroPool (sizeof (EFI_DHCP4_PACKET_OPTION));
  if (ParaList[1] == NULL) {
    Status = EFI_OUT_OF_RESOURCES;
    goto ON_EXIT;
  }
  
  ParaList[1]->OpCode  = DHCP4_TAG_PARA_LIST;
  ParaList[1]->Length  = 1;
  ParaList[1]->Data[0] = DHCP4_TAG_DNS_SERVER;

  Status = Dhcp4->Build (Dhcp4, &SeedPacket, 0, NULL, 2, ParaList, &Token.Packet); 

  Token.Packet->Dhcp4.Header.Xid      = HTONL(NET_RANDOM (NetRandomInitSeed ()));
  
  Token.Packet->Dhcp4.Header.Reserved = HTONS ((UINT16)0x8000);
  
  if (Instance->Dns4CfgData.UseDefaultSetting) {
    CopyMem (&(Token.Packet->Dhcp4.Header.ClientAddr), &(InterfaceInfo->StationAddress), sizeof (EFI_IPv4_ADDRESS));
  } else {
    CopyMem (&(Token.Packet->Dhcp4.Header.ClientAddr), &(Instance->Dns4CfgData.StationIp), sizeof (EFI_IPv4_ADDRESS));
  }
  
  CopyMem (Token.Packet->Dhcp4.Header.ClientHwAddr, &(InterfaceInfo->HwAddress), InterfaceInfo->HwAddressSize); 
  
  Token.Packet->Dhcp4.Header.HwAddrLen = (UINT8)(InterfaceInfo->HwAddressSize);

  //
  // TransmitReceive Token
  //
  Status = Dhcp4->TransmitReceive (Dhcp4, &Token);
  if (EFI_ERROR (Status)) {
    goto ON_EXIT;
  }

  //
  // Poll the packet
  //
  do {
    Status = Mnp->Poll (Mnp);
  } while (!IsDone);
  
  //
  // Parse the ACK to get required information if received done.
  //
  if (IsDone && !EFI_ERROR (Token.Status)) {
    for (Index = 0; Index < Token.ResponseCount; Index++) {
      Status = ParseDhcp4Ack (Dhcp4, &Token.ResponseList[Index], &DnsServerInfor);
      if (!EFI_ERROR (Status)) {
        break;
      }
    }

    *DnsServerList = DnsServerInfor.ServerList;
  } else {
    Status = Token.Status;
  }
  
ON_EXIT:

  if (Data != NULL) {
    FreePool (Data);
  }

  for (Index = 0; Index < 2; Index++) {
    if (ParaList[Index] != NULL) {
      FreePool (ParaList[Index]);
    }
  }

  if (Token.ListenPoints) {
    FreePool (Token.ListenPoints);
  }

  if (Token.Packet) {
    FreePool (Token.Packet);
  }
  
  if (Token.ResponseList != NULL) {
    FreePool (Token.ResponseList);
  }
  
  if (Token.CompletionEvent != NULL) {
    gBS->CloseEvent (Token.CompletionEvent);
  }
  
  if (Dhcp4 != NULL) {
    Dhcp4->Stop (Dhcp4);
    Dhcp4->Configure (Dhcp4, NULL);

    gBS->CloseProtocol (
           Dhcp4Handle,
           &gEfiDhcp4ProtocolGuid,
           Image,
           Controller
           );
  }
  
  if (Dhcp4Handle != NULL) {
    NetLibDestroyServiceChild (
      Controller,
      Image,
      &gEfiDhcp4ServiceBindingProtocolGuid,
      Dhcp4Handle
      );
  }

  if (Mnp != NULL) {
    Mnp->Configure (Mnp, NULL);

    gBS->CloseProtocol (
           MnpChildHandle,
           &gEfiManagedNetworkProtocolGuid,
           Image,
           Controller
           );
  }
  
  NetLibDestroyServiceChild (
    Controller,
    Image,
    &gEfiManagedNetworkServiceBindingProtocolGuid,
    MnpChildHandle
    );
  
  return Status;
}

/**
  Parse the DHCP ACK to get Dns6 server information.

  @param  Image            The handle of the driver image.
  @param  Controller       The handle of the controller.
  @param  DnsServerCount   Retrieved Dns6 server Ip count.
  @param  DnsServerList    Retrieved Dns6 server Ip list.

  @retval EFI_SUCCESS           The Dns6 information is got from the DHCP ACK.
  @retval EFI_OUT_OF_RESOURCES  Failed to allocate memory.
  @retval EFI_NO_MEDIA          There was a media error.
  @retval Others                Other errors as indicated.

**/
EFI_STATUS
GetDns6ServerFromDhcp6 (
  IN  EFI_HANDLE                 Image,
  IN  EFI_HANDLE                 Controller,
  OUT UINT32                     *DnsServerCount,
  OUT EFI_IPv6_ADDRESS           **DnsServerList
  )
{
  EFI_HANDLE                Dhcp6Handle;
  EFI_DHCP6_PROTOCOL        *Dhcp6;
  EFI_STATUS                Status;
  EFI_STATUS                TimerStatus;
  EFI_DHCP6_PACKET_OPTION   *Oro;
  EFI_DHCP6_RETRANSMISSION  InfoReqReXmit;
  EFI_EVENT                 Timer;
  BOOLEAN                   MediaPresent;
  DNS6_SERVER_INFOR         DnsServerInfor;

  Dhcp6Handle = NULL;
  Dhcp6       = NULL;
  Oro         = NULL;
  Timer       = NULL;

  ZeroMem (&DnsServerInfor, sizeof (DNS6_SERVER_INFOR));

  DnsServerInfor.ServerCount = DnsServerCount;

  //
  // Check media status before doing DHCP.
  //
  MediaPresent = TRUE;
  NetLibDetectMedia (Controller, &MediaPresent);
  if (!MediaPresent) {
    return EFI_NO_MEDIA;
  }

  //
  // Create a DHCP6 child instance and get the protocol.
  //
  Status = NetLibCreateServiceChild (
             Controller,
             Image,
             &gEfiDhcp6ServiceBindingProtocolGuid,
             &Dhcp6Handle
             );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  Status = gBS->OpenProtocol (
                  Dhcp6Handle,
                  &gEfiDhcp6ProtocolGuid,
                  (VOID **) &Dhcp6,
                  Image,
                  Controller,
                  EFI_OPEN_PROTOCOL_BY_DRIVER
                  );
  if (EFI_ERROR (Status)) {
    goto ON_EXIT;
  }

  Oro = AllocateZeroPool (sizeof (EFI_DHCP6_PACKET_OPTION) + 1);
  if (Oro == NULL) {
    Status = EFI_OUT_OF_RESOURCES;
    goto ON_EXIT;
  }

  //
  // Ask the server to reply with DNS options.
  // All members in EFI_DHCP6_PACKET_OPTION are in network order.
  //
  Oro->OpCode  = HTONS (DHCP6_TAG_DNS_REQUEST);
  Oro->OpLen   = HTONS (2);
  Oro->Data[1] = DHCP6_TAG_DNS_SERVER;

  InfoReqReXmit.Irt = 4;
  InfoReqReXmit.Mrc = 1;
  InfoReqReXmit.Mrt = 10;
  InfoReqReXmit.Mrd = 30;

  Status = Dhcp6->InfoRequest (
                    Dhcp6,
                    TRUE,
                    Oro,
                    0,
                    NULL,
                    &InfoReqReXmit,
                    NULL,
                    ParseDhcp6Ack,
                    &DnsServerInfor
                    );
  if (Status == EFI_NO_MAPPING) {
    Status = gBS->CreateEvent (EVT_TIMER, TPL_CALLBACK, NULL, NULL, &Timer);
    if (EFI_ERROR (Status)) {
      goto ON_EXIT;
    }

    Status = gBS->SetTimer (
                    Timer,
                    TimerRelative,
                    DNS_TIME_TO_GETMAP * TICKS_PER_SECOND
                    );

    if (EFI_ERROR (Status)) {
      goto ON_EXIT;
    }

    do {
      TimerStatus = gBS->CheckEvent (Timer);
      if (!EFI_ERROR (TimerStatus)) {
        Status = Dhcp6->InfoRequest (
                          Dhcp6,
                          TRUE,
                          Oro,
                          0,
                          NULL,
                          &InfoReqReXmit,
                          NULL,
                          ParseDhcp6Ack,
                          &DnsServerInfor
                          );
      }
    } while (TimerStatus == EFI_NOT_READY);
  }
  
  *DnsServerList  = DnsServerInfor.ServerList;

ON_EXIT:

  if (Oro != NULL) {
    FreePool (Oro);
  }  

  if (Timer != NULL) {
    gBS->CloseEvent (Timer);
  }

  if (Dhcp6 != NULL) {
    gBS->CloseProtocol (
           Dhcp6Handle,
           &gEfiDhcp6ProtocolGuid,
           Image,
           Controller
           );
  }

  NetLibDestroyServiceChild (
    Controller,
    Image,
    &gEfiDhcp6ServiceBindingProtocolGuid,
    Dhcp6Handle
    );

  return Status;
  
}

